package com.yt.javafxboot.ctrl;

import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSONPath;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.TypeRef;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.yt.javafxboot.common.MyStaticOutputStreamAppender;
import com.yt.javafxboot.entity.Magazine;
import com.yt.javafxboot.entity.Mapo;
import com.yt.javafxboot.entity.MapoIdCode;
import com.yt.javafxboot.util.DateUtil;
import com.yt.javafxboot.util.FtpUtils;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.XML;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import de.felixroske.jfxsupport.FXMLController;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.stage.FileChooser;
import lombok.extern.slf4j.Slf4j;

/**
 * 主界面控制器
 *
 * @author westinyang
 * @date 2019/4/23 2:01
 */
@Slf4j
@FXMLController
@EnableScheduling
public class MainCtrl implements Initializable {
    public static int curFrequency = 0;
    public static boolean isActive = false;
    public static Process process = null;
    public static ExecutorService pool = null;
    static Lock lock = new ReentrantLock(true);
    private static ConcurrentHashMap<String, String> mapoIdcodeMap = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String, String> mapoIdcodeXmlMap = new ConcurrentHashMap<>();

    // 主容器
    public Pane rootPane;
    /**
     * 可执行文件路径
     */
    public TextField exePath;
    /***
     * 频率 TextField
     */
    public TextField frequency;

    public TextArea mapoIdCodeDatail;
    public TextArea logTextArea;
    public TextField cellName;
    /**
     * 频率设置
     */
    public Slider frequencySlider;
    public Button activeButton;
    public Button stopButton;
    public Circle activeCircle;
    public Circle stopCircle;

    /**
     * 是否进程等待
     */
    public CheckBox processWait;
    /**
     * FTP ip
     */
    public TextField ftpIP;
    /**
     * FTP 账号
     */
    public TextField ftpACT;

    public Button clearLog;
    /**
     * FTP 密码
     */
    public PasswordField ftpPWD;
    ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build();
    String lastJsonStr = null;

    String mapoIdCodeDatailKey = "";
    @FXML
    private TableView<MapoIdCode> tbMapo;

    public static void main(String[] args) {
        String jsonStr = "{\n" +
                "\t\"cells\": {\n" +
                "\t\t\"cell\": {\n" +
                "\t\t\t\"cellname\": 613,\n" +
                "\t\t\t\"magazine\": [{\n" +
                "\t\t\t\t\"magazinedesc\": \"T6L-360\",\n" +
                "\t\t\t\t\"magazineno\": 1,\n" +
                "\t\t\t\t\"magpos\": [{\n" +
                "\t\t\t\t\t\"idcode\": \"0007BDFCD9\",\n" +
                "\t\t\t\t\t\"magposno\": 1\n" +
                "\t\t\t\t}, {\n" +
                "\t\t\t\t\t\"idcode\": \"0007AB1DBC\",\n" +
                "\t\t\t\t\t\"magposno\": 2\n" +
                "\t\t\t\t}, {\n" +
                "\t\t\t\t\t\"idcode\": \"\",\n" +
                "\t\t\t\t\t\"magposno\": 30\n" +
                "\t\t\t\t}]\n" +
                "\t\t\t}, {\n" +
                "\t\t\t\t\"magazinedesc\": \"Load Magazine\",\n" +
                "\t\t\t\t\"magazineno\": 2,\n" +
                "\t\t\t\t\"magpos\": [{\n" +
                "\t\t\t\t\t\"idcode\": \"\",\n" +
                "\t\t\t\t\t\"magposno\": 1\n" +
                "\t\t\t\t}, {\n" +
                "\t\t\t\t\t\"idcode\": \"\",\n" +
                "\t\t\t\t\t\"magposno\": 2\n" +
                "\t\t\t\t}]\n" +
                "\t\t\t}]\n" +
                "\t\t}\n" +
                "\t}\n" +
                "}";
        final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
        // 找出不一样的

        List<Magazine> read = JsonPath.using(
                Configuration.builder()
                        .jsonProvider(new JacksonJsonProvider(mapper))
                        .mappingProvider(new JacksonMappingProvider(mapper))
                        .build()
        ).parse(jsonStr).read("$.cells.cell.magazine[*]", new TypeRef<List<Magazine>>() {
        });

        read.forEach(e -> {
            List<Mapo> mapos = e.getMagpos();
            if (!CollectionUtils.isEmpty(mapos)) {
                mapos.forEach(mapo -> {
                    String key = e.getMagazinedesc().concat(e.getMagazineno()).concat(mapo.getMagposno());
                    String value = mapo.getIdcode();
                    if (mapoIdcodeMap.containsKey(key) && !value.equals(mapoIdcodeMap.get(key))) {
                        // 调用接口

                        // 放入本地缓存
                        mapoIdcodeMap.put(key, value);
                        // 放入数据库

                    } else {
                        // 从数据库中取
                        // 如果不一致，则调用接口
                        // 如果不一致 放入数据库
                        // 放入本地缓存
                    }
                });
            }
        });
        List<Mapo> read2 = JsonPath.using(
                Configuration.builder()
                        .jsonProvider(new JacksonJsonProvider(mapper))
                        .mappingProvider(new JacksonMappingProvider(mapper))
                        .build()
        ).parse(jsonStr).read("$.cells.cell.magazine[*].magpos[*]", new TypeRef<List<Mapo>>() {
        });

        read.forEach(e -> {
            System.out.println(e.toString());
        });
        read2.forEach(e -> {
            System.out.println(e.toString());
        });
    }

    @Override
    @FXML
    public void initialize(URL location, ResourceBundle resources) {
        cellName.setText("613");
        pool = new ThreadPoolExecutor(1, // 核心线程数
                1, // 最大线程
                0L, TimeUnit.MILLISECONDS, // 存活时间
                new LinkedBlockingQueue<Runnable>(1024), // 最大工作队列
                threadFactory, // 线程工厂
                new ThreadPoolExecutor.AbortPolicy()); // 线程拒绝策略;
        changeStatus(isActive);

        int initFrequency = (int) Math.ceil(frequencySlider.getValue());
        curFrequency = initFrequency;
        frequency.setText(String.valueOf(initFrequency));
        frequencySlider.valueProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                if (Math.ceil(oldValue.doubleValue()) != Math.ceil(newValue.doubleValue())) {
                    curFrequency = (int) Math.ceil(newValue.doubleValue());
                    System.out.println(curFrequency);
                    frequency.setText(String.valueOf(curFrequency));
                }
            }
        });

        exePath.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (!StringUtils.isEmpty(oldValue) && !oldValue.equals(newValue)) {
                    changeInactive();
                }
            }
        });


        ftpIP.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (!StringUtils.isEmpty(oldValue) && !oldValue.equals(newValue)) {
                    changeInactive();
                }
                System.out.println(oldValue);
                System.out.println(newValue);
                FtpUtils.hostname = newValue;
            }

        });

        ftpACT.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (!StringUtils.isEmpty(oldValue) && !oldValue.equals(newValue)) {
                    changeInactive();
                }
                FtpUtils.username = newValue;
                System.out.println(oldValue);
                System.out.println(newValue);
            }
        });


        ftpPWD.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (!StringUtils.isEmpty(oldValue) && !oldValue.equals(newValue)) {
                    changeInactive();
                }
                FtpUtils.password = newValue;
            }
        });

        tbMapo.setRowFactory(tv -> {
            TableRow<MapoIdCode> row = new TableRow<>();
            row.setOnMouseClicked(event -> {
                if (event.getClickCount() == 1 && (!row.isEmpty())) {
                    MapoIdCode rowData = row.getItem();
                    mapoIdCodeDatailKey = rowData.getMapo().concat("_").concat(rowData.getIdCode());
                    String xml = mapoIdcodeXmlMap.get(mapoIdCodeDatailKey);
                    mapoIdCodeDatail.setText(xml);
                    System.out.println(rowData);
                }
            });
            return row;
        });
        cellName.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                cellName.setText(newValue);
            }
        });

        Button btn = new Button();
        btn.setText("Log stuff");
        btn.setOnAction(a -> {
            log.info("This is some info");
            log.error("This is some error");
        });


        processWait.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                log.info("进程等待 : " + newValue);
            }
        });

        OutputStream os = new TextAreaOutputStream(logTextArea);
        MyStaticOutputStreamAppender.setStaticOutputStream(os);

        log.info("initialize: {}", location.getPath());
    }

    /**
     * @param mouseEvent
     */
    public void clearLog(MouseEvent mouseEvent) {
        logTextArea.setText("");
    }

    private static class TextAreaOutputStream extends OutputStream {

        private TextArea textArea;

        public TextAreaOutputStream(TextArea textArea) {
            this.textArea = textArea;
        }

        @Override
        public void write(int b) throws IOException {
            Platform.runLater(() -> {
                textArea.appendText(String.valueOf((char) b));
            });
        }
    }

    private void changeStatus(boolean isActive) {
        stopButton.visibleProperty().setValue(isActive);
        activeButton.visibleProperty().setValue(!isActive);
        activeCircle.visibleProperty().setValue(isActive);
        stopCircle.visibleProperty().setValue(!isActive);
    }

    @Scheduled(fixedRate = 1000)
    public void refreshTable() {
        if (StringUtils.isEmpty(exePath) || StringUtils.isEmpty(frequency)) {

        }
        List<MapoIdCode> mapoIdCodes = new ArrayList<>();
        if (null != tbMapo) {

            if (!CollectionUtils.isEmpty(mapoIdcodeMap)) {
                mapoIdcodeMap.forEach((key, val) -> {
                    MapoIdCode mapoIdCode = new MapoIdCode();
                    mapoIdCode.setMapo(key);
                    mapoIdCode.setIdCode(val);
                    mapoIdCodes.add(mapoIdCode);
                });
            }
            List<MapoIdCode> list = new ArrayList<>();
            if (isActive) {
                list = mapoIdCodes.stream().sorted(Comparator.comparing(MapoIdCode::getMapo))
                        .collect(Collectors.toList());
                if (!StringUtils.isEmpty(mapoIdCodeDatailKey) && !CollectionUtils.isEmpty(mapoIdcodeXmlMap) && mapoIdcodeXmlMap.containsKey(mapoIdCodeDatailKey)) {
                    String xml = mapoIdcodeXmlMap.get(mapoIdCodeDatailKey);
                    mapoIdCodeDatail.setText(xml);
                }
            } else {
                mapoIdCodeDatail.setText("");
            }


            ObservableList<MapoIdCode> data = FXCollections.observableArrayList(list);
            ObservableList<TableColumn<MapoIdCode, ?>> observableList = tbMapo.getColumns();
            observableList.get(0).setCellValueFactory(new PropertyValueFactory<>("mapo"));
            observableList.get(1).setCellValueFactory(new PropertyValueFactory<>("idCode"));
            tbMapo.setItems(data);


        }

    }

    public void executeDataColletion(InputMethodEvent inputMethodEvent) {

        // 更多请阅读：https://www.yiibai.com/javafx/javafx_filechooser.html

    }

    /**
     * 选择MPS2WSM.exe 可执行文件。
     *
     * @param mouseEvent
     */
    public void chooseTargetExe(MouseEvent mouseEvent) {
        FileChooser fileChooser = new FileChooser();
        FileChooser.ExtensionFilter exeExtFilter = new FileChooser.ExtensionFilter("EXE files (*.exe)", new String[]{
                "*.exe"
        });
        fileChooser.getExtensionFilters().add(exeExtFilter);
        File file = fileChooser.showOpenDialog(rootPane.getScene().getWindow());
        if (null != file) {
            exePath.setText(file.getAbsolutePath());
        }
    }

    public void changeFrequency(KeyEvent keyEvent) {
        System.out.println(frequencySlider.getValue());
    }

    public void activeStatus(MouseEvent mouseEvent) {
        changeActive();
    }

    private void changeActive() {
        isActive = true;
        changeStatus(true);
        executeDataColletion();
    }

    public void inactiveStatus(MouseEvent mouseEvent) {
        changeInactive();
    }

    private void changeInactive() {
        isActive = false;
        changeStatus(false);
        if (null != process) {
            process.destroy();
        }
        if (null != pool) {
            pool.shutdown();
            pool = null;
        }
    }

    public void executeDataColletion() {
        // 判断频率 和 exe 路径是否存在
        if (StringUtils.isEmpty(exePath.getText())) {
            alertError("请选择可执行文件");
            return;
        }
        // 判断频率 和 exe 路径是否存在
        if (StringUtils.isEmpty(frequency.getText())) {
            alertError("请选择频率");
            return;
        }

        // FTP IP
        if (StringUtils.isEmpty(FtpUtils.hostname)) {
            alertError("请填写FTP IP");
            return;
        } else if (!FtpUtils.isValidIPAddress(FtpUtils.hostname)) {
            alertError("FTP IP 不合法");
            return;
        }

        // FTP 账号
        if (StringUtils.isEmpty(FtpUtils.username)) {
            alertError("请填写FTP 账号");
            return;
        }
        // FTP 密码
        if (StringUtils.isEmpty(FtpUtils.password)) {
            alertError("请填写FTP 密码");
            return;
        }
        // 判断频率 和 exe 路径是否存在
        if (StringUtils.isEmpty(cellName.getText())) {
            alertError("请填写Cell Name");
            return;
        }
        if (isActive) {
            if (null != process) {
                process.destroy();
            }

            if (null == pool) {
                pool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024),
                        threadFactory, new ThreadPoolExecutor.AbortPolicy());
            }
            pool.execute(() -> {
                while (true) {
                    lock.lock();
                    try {
                        if (null != pool && !pool.isShutdown()) {
                            // 如果MPS2WSM.exe 没有在一个周期（curFrequency），执行完 就杀掉有问题，
                            // MPS2WSM.exe生成文件1秒自动关，不需要手动关闭，确认后周期 屏蔽process.destroy();
                            if (null != process) {
                                process.destroy();
                            }
                            // 这里采用子线程执行
                            // 1、创建目标目录，文件按照年月日归类存储，检查当天目录，不存在则创建目录，比如，今天是2022.6.11 则创建目录20220611
                            // ftp 上传
                            // ftp 搭建 参考  http://www.kkx.net/soft/30153.html，注意ftp 目录的创建与写入权限
                            String fileName = "cellcontent_".concat(DateUtil.getNowFullDay()).concat(".xml");
                            String targetDir = DateUtil.getNowFullDay();
                            String sourceDir = "C:\\temp";
                            String filePath = sourceDir + "\\" + fileName;
                            String cmd = exePath.getText()
                                    + " /EXPORTCELLCONTENT \"" + filePath + "\" \"" + cellName.getText() + "\"";
                            log.info("[ EXPORTCELLCONTENT ] " + cmd);
                            process = Runtime.getRuntime().exec(cmd);
                            try {
                                if (processWait.isSelected()) {
                                    log.info("process 进程等待。。");
                                    process.waitFor();
                                }
                            } catch (InterruptedException e) {
                                log.error(e.toString());
                            }
                            SAXReader saxReader = new SAXReader();
                            File file = new File(filePath);
                            if (file.exists()) {
                                Document document = saxReader.read(file);
                                String xml = document.asXML();
                                org.json.JSONObject xmlJSONObj = null;
                                try {
                                    xmlJSONObj = XML.toJSONObject(xml);

                                    String jsonStr = xmlJSONObj.toString();
                                    // 和上次不一样
                                    if (!jsonStr.equals(lastJsonStr)) {
                                        lastJsonStr = jsonStr;
                                        FtpUtils ftp = new FtpUtils();
                                        ftp.uploadFile("/" + targetDir + "/", fileName, filePath);
                                        final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
                                        List<Magazine> read = JsonPath.using(
                                                Configuration.builder()
                                                        .jsonProvider(new JacksonJsonProvider(mapper))
                                                        .mappingProvider(new JacksonMappingProvider(mapper))
                                                        .build()
                                        ).parse(jsonStr).read("$.cells.cell.magazine[*]", new TypeRef<List<Magazine>>() {
                                        });

                                        read.forEach(e -> {
                                            List<Mapo> mapos = e.getMagpos();
                                            if (!CollectionUtils.isEmpty(mapos)) {
                                                mapos.forEach(mapo -> {
                                                    String key = e.getMagazinedesc().concat("_").concat(e.getMagazineno()).concat("_").concat(mapo.getMagposno());
                                                    String value = mapo.getIdcode();
                                                    if (!StringUtils.isEmpty(value) && !value.equals(mapoIdcodeMap.get(key))) {
                                                        String palletFileName = "mypallet_".concat(targetDir).concat(".xml");
                                                        String palletFilePath = sourceDir + "\\" + palletFileName;
                                                        // 调用接口
                                                        String cmd2 = exePath.getText()
                                                                + " /EXPORTPALLET " + value + " \"" + palletFilePath + "\"";
                                                        log.info("[ EXPORTPALLET ] " + cmd2);
                                                        try {
                                                            Process process2 = Runtime.getRuntime().exec(cmd2);
                                                            try {
                                                                if (processWait.isSelected()) {
                                                                    log.info("process2 进程等待。。");
                                                                    process2.waitFor();
                                                                }


                                                            } catch (InterruptedException ex) {
                                                                log.error(ex.toString());
                                                            }
                                                            // 上传
                                                            ftp.uploadFile("/" + targetDir + "/", palletFileName, palletFilePath);

                                                            process2.destroy();

                                                            File palletFile = new File(palletFilePath);
                                                            if (palletFile.exists()) {

                                                                Document palletFileDocument = saxReader.read(palletFile);
                                                                String palletFileXml = palletFileDocument.asXML();
//                                                        JSONObject palletFileXmlSONObj = XML.toJSONObject(palletFileXml);
                                                                if (!StringUtils.isEmpty(key) && !StringUtils.isEmpty(mapoIdcodeMap.get(key)) && mapoIdcodeXmlMap.containsKey(key.concat("_").concat(mapoIdcodeMap.get(key)))) {
                                                                    mapoIdcodeXmlMap.remove(key.concat("_").concat(mapoIdcodeMap.get(key)));
                                                                }

                                                                mapoIdcodeXmlMap.put(key.concat("_").concat(value), format(palletFileXml));
                                                            }

                                                        } catch (IOException e1) {
                                                            log.error(e1.toString());
                                                        } catch (DocumentException e1) {
                                                            log.error(e1.toString());
                                                        }

                                                    }
                                                    // 放入本地缓存
                                                    mapoIdcodeMap.put(key, value);
                                                });

                                            }
                                            System.out.println(e.toString());
                                        });

                                    }
                                    System.out.println("json:" + xmlJSONObj.toString());
                                    log.debug("json:" + xmlJSONObj.toString());
                                } catch (JSONException e) {
                                    log.error(e.toString());
                                }
                                System.err.println("当前时间 " + new Date());
                            }
                        }
                    } catch (Exception e) {
                        log.error(e.toString());
                    } finally {
                        try {
                            Thread.sleep(1000 * curFrequency);
                        } catch (InterruptedException e) {
                            log.error(e.toString());
                        }
                        lock.unlock();
                    }
                }
            });
        }
    }

    public String format(String str) {
        StringWriter out = new StringWriter();
        SAXReader reader = new SAXReader();
        StringReader in = new StringReader(str);
        Document doc;
        try {
            doc = reader.read(in);
            OutputFormat formater =
                    OutputFormat.createPrettyPrint();
            formater.setEncoding("utf-8");
            XMLWriter writer = new XMLWriter(out, formater);
            writer.setEscapeText(false);
            writer.write(doc);
            writer.close();
        } catch (DocumentException e) {
            log.error(e.toString());
        } catch
        (IOException e) {
            log.error(e.toString());
        }
        return out.toString();
    }

    private void alertError(String content) {
        changeInactive();
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.getDialogPane().getStyleClass().add("bg");

//        Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
        alert.setTitle("错误");
        alert.setHeaderText(null);
        alert.setContentText(content);
        alert.initStyle(StageStyle.UNDECORATED);
        alert.showAndWait();
    }

}
